module klang.编译.工具.工具_;
import 全局;
import std.conv: ·to = to;
import std.functional: ·fd = toDelegate;
import 核心 = klang.编译.核心.核心_;
import 路径模块 = klang.编译.路径.路径_;
import 编译数字 = 编译数字.编译;
import klang.编译.核心.核心_: 整数_, 创建数组;
import klang.编译.类型.枚举值.枚举值_: SK, 节点标志_, 字符_, 比较结果_, Extension;
import klang.编译.类型.类型_: 符号_, 符号表_;
import klang.编译.语法节点.节点_: 源文件_, 节点_;
import klang.编译.路径.路径_: 路径_;
import klang.编译.扫描工具.扫描工具_: 跳过杂项;
import klang.编译.诊断.诊断_: 诊断_, 诊断消息_, 诊断消息_链_, 诊断包括位置_, 诊断相关信息_;
/** commonPackageFolders */
dstring[] 通用软件包文件夹;
/** implicitExcludePathRegexPattern */
dstring 隐式排除路径正则表达式模式;
/** reservedCharacterPattern */
正则_!(dchar) 保留字符模式;
int[] 通配符字符代码数组;
klang.编译.工具.工具_.通配符匹配器_ 文件匹配器;
klang.编译.工具.工具_.通配符匹配器_ 目录匹配器;
klang.编译.工具.工具_.通配符匹配器_ 排除匹配器;
dstring[dstring] 本地的诊断消息;
/**
 *  List of supported extensions in order of file resolution precedence.
 */
Extension[] supportedTSExtensions;
Extension[] supportedTSExtensionsWithJson;
/** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */
Extension[] supportedTSExtensionsForExtractExtension;
Extension[] supportedJSAndJsonExtensions;
Extension[] allSupportedExtensions;
Extension[] allSupportedExtensionsWithJson;
static this() {
    通用软件包文件夹 = ["模块"d];
    隐式排除路径正则表达式模式 = `(?!(`d ~ 核心.连接(.通用软件包文件夹, "|"d) ~ `)(/|$))`d;
    保留字符模式 = ·regex(r"[^\w\s\/]"d, "g"c);
    通配符字符代码数组 = [字符_.星号, 字符_.问号];
    文件匹配器 = new klang.编译.工具.工具_.通配符匹配器_("([^./]|(\\.(?!min\\.js$))?)*"d, `(/`d ~ .隐式排除路径正则表达式模式 ~ `[^/.][^/]*)*?`d);
    目录匹配器 = new klang.编译.工具.工具_.通配符匹配器_("[^/]*"d, `(/`d ~ .隐式排除路径正则表达式模式 ~ `[^/.][^/]*)*?`d);
    排除匹配器 = new klang.编译.工具.工具_.通配符匹配器_("[^/]*"d, "(/.+?)?"d);
    supportedTSExtensions = [Extension.K, Extension.Dk];
    supportedTSExtensionsWithJson = [Extension.K, Extension.Dk, Extension.Json];
    supportedTSExtensionsForExtractExtension = [Extension.Dk, Extension.K];
    supportedJSAndJsonExtensions = [Extension.Json];
    allSupportedExtensions = ··spread!(Extension)(cast()(.supportedTSExtensions));
    allSupportedExtensionsWithJson = ··spread!(Extension)(cast()(.supportedTSExtensions), [Extension.Json]);
}
class 文件系统条目_ {
    dstring[] 文件组;
    dstring[] 目录组;
    this() {
        this.文件组 = [];
        this.目录组 = [];
    }
    this(dstring[] 文件, dstring[] 目录) {
        this.文件组 = 文件;
        this.目录组 = 目录;
    }
}
/** FileMatcherPatterns */
class 文件匹配器模式_ {
    dstring 排除模式;
    dstring[] 基路径组;
    this() { }
    this(dstring 排除 = ··null!(dstring), dstring[] 基路径 = ··null!(dstring[])) {
        this.排除模式 = 排除;
        this.基路径组 = 基路径;
    }
}
class 通配符匹配器_ {
    dstring 单星号正则表达式片段;
    dstring 双星号正则表达式片段;
    this() { }
    this(dstring 单星号片段, dstring 双星号片段) {
        this.单星号正则表达式片段 = 单星号片段;
        this.双星号正则表达式片段 = 双星号片段;
    }
    dstring 替换通配符(匹配结果组_!(dstring) 匹配) {
        return .替换通配符(匹配.hit(), this.单星号正则表达式片段);
    }
}
class 诊断收集_ {
    诊断_[] nonFileDiagnostics;
    路径模块.路径_[] filesWithDiagnostics;
    核心.词典_!(诊断_[]) fileDiagnostics;
    bool hasReadNonFileDiagnostics;
    this() {
        this.nonFileDiagnostics = 创建数组!(诊断_)();
        this.filesWithDiagnostics = 创建数组!(路径模块.路径_)();
        this.fileDiagnostics = 核心.创建词典!(诊断_[])();
        this.hasReadNonFileDiagnostics = false;
    }
    void add(诊断_ diagnostic) {
        if (diagnostic.文件 !is ··null!(Object)) {
            if (!this.fileDiagnostics.具有((cast(源文件_)(diagnostic.文件)).文件名)) {
                this.fileDiagnostics.p·设置((cast(源文件_)(diagnostic.文件)).文件名, 创建数组!(诊断_)());
                核心.插入排序的!(路径模块.路径_)(this.filesWithDiagnostics, (cast(源文件_)(diagnostic.文件)).文件名, cast(核心.比较器_!(路径模块.路径_))(·fd(&核心.比较文本区分大小写)));
            }
            诊断_[] 结果 = this.fileDiagnostics.获取((cast(源文件_)(diagnostic.文件)).文件名);
            核心.插入排序的!(诊断_)(结果, diagnostic, cast(核心.比较器_!(诊断_))(·fd(&.比较诊断)));
            this.fileDiagnostics.p·设置((cast(源文件_)(diagnostic.文件)).文件名, 结果);
        }
        else {
            // If we've already read the non-file 结果, do not modify the existing array.
            if (this.hasReadNonFileDiagnostics) {
                this.hasReadNonFileDiagnostics = false;
                this.nonFileDiagnostics = this.nonFileDiagnostics[0 .. $];
            }
            核心.插入排序的!(诊断_)(this.nonFileDiagnostics, diagnostic, cast(核心.比较器_!(诊断_))(·fd(&.比较诊断)));
        }
    }
    诊断_ lookup(诊断_ diagnostic) {
        诊断_[] 结果;
        if ((diagnostic.文件 !is ··null!(Object)) && this.fileDiagnostics.具有((cast(源文件_)(diagnostic.文件)).文件名)) {
            结果 = this.fileDiagnostics.获取((cast(源文件_)(diagnostic.文件)).文件名);
        }
        else {
            结果 = this.nonFileDiagnostics;
        }
        if (结果 is ··null!(诊断_[])) {
            return ··null!(诊断_);
        }
        long result = 核心.二分搜索!(诊断_, 诊断_)(结果, diagnostic, cast(诊断_ delegate(诊断_))(·fd(&核心.不改变!(诊断_))), cast(比较结果_ delegate(诊断_, 诊断_))(·fd(&.比较诊断跳过相关消息)));
        if (result >= 0) {
            return 结果[result];
        }
        return ··null!(诊断_);
    }
    诊断_[] 获取全局诊断组() {
        this.hasReadNonFileDiagnostics = true;
        return this.nonFileDiagnostics;
    }
    诊断_[] 获取诊断组() {
        诊断_[] fileDiags = 核心.扁平化映射组!(路径模块.路径_, 诊断_)(this.filesWithDiagnostics, (路径_ f, size_t _) {
            if (this.fileDiagnostics.具有(f)) {
                return this.fileDiagnostics.获取(f);
            }
            return (cast(诊断_[])([]));
        });
        if (this.nonFileDiagnostics.length == 0) {
            return fileDiags;
        }
        诊断_[] 结果 = this.nonFileDiagnostics;
        if (fileDiags !is ··null!(诊断_[])) {
            return .追加!(诊断_)(结果, fileDiags);
        }
        return 结果;
    }
    诊断_[] 获取诊断组(dstring fileName) {
        if (this.fileDiagnostics.具有(fileName)) {
            return this.fileDiagnostics.获取(fileName);
        }
        return ··null!(诊断_[]);
    }
    void reattachFileDiagnostics(源文件_ newFile) {
        if (this.fileDiagnostics.具有(newFile.文件名)) {
            核心.循环每个!(诊断_, bool)(this.fileDiagnostics.获取(newFile.文件名), (诊断_ diagnostic, size_t _) {
                diagnostic.文件 = newFile;
                return false;
            });
        }
    }
}
/** getRegexFromPattern */
正则_!(dchar) 获取正则从模式(dstring 模式, bool 使用区分大小写的文件名) {
    return .创建正则!(dchar)(模式, 使用区分大小写的文件名 ? ""c : "i"c);
}
/** replaceWildcardCharacter */
dstring 替换通配符(dstring 匹配, dstring 单星号片段) {
    return 匹配 == "*"d ? 单星号片段 : 匹配 == "?"d ? "[^/]"d : "\\"d ~ 匹配;
}
/** getRegularExpressionsForWildcards */
dstring[] 获取通配符的正则表达式组(dstring[] 规范组 = ··null!(dstring[]), dstring 基路径 = ··null!(dstring), dstring 使用者 = ··null!(dstring)) {
    if ((规范组 is ··null!(dstring[])) || 规范组.length == 0) {
        return ··null!(dstring[]);
    }
    return 核心.扁平化映射!(dstring, dstring)(规范组, (dstring 规范, size_t _) {
        if (规范 !is ··null!(dstring)) {
            通配符匹配器_ 通配 = .文件匹配器;
            if (使用者 == "目录"d) {
                通配 = .目录匹配器;
            }
            else if (使用者 == "排除"d) {
                通配 = .排除匹配器;
            }
            return .获取子模式从规范(规范, 基路径, 使用者, 通配);
        }
        return ··null!(dstring);
    });
}
/** isImplicitGlob */
bool 是隐式Glob(dstring 最后路径组件) {
    正则_!(dchar) 正则 = ·regex(r"[.*?]"d, "g"c);
    return 正则.p·匹配(最后路径组件).empty();
}
/** getSubPatternFromSpec */
dstring 获取子模式从规范(dstring 规范, dstring 基路径, dstring 使用者, 通配符匹配器_ 通配) {
    dstring 子模式 = ""d;
    bool 有通配符组件 = false;
    dstring[] 组件组 = 路径模块.获取正规化路径组件组(规范, 基路径);
    dstring 最后组件 = 核心.最后一个!(dstring)(组件组);
    if (使用者 != "排除"d && 最后组件 == "**"d) {
        return ··null!(dstring);
    }
    // getNormalizedPathComponents 包扩组 the separator for the root 组件.
    // We need to remove to create our regex correctly.
    组件组[0] = 路径模块.删除尾随路径分隔符(组件组[0]);
    if (.是隐式Glob(最后组件)) {
        组件组 = .追加!(dstring)(组件组, "**"d, "*"d);
    }
    int 可选计数 = 0;
    foreach (dstring 组件; 组件组) {
        if (组件 == "**"d) {
            子模式 ~= 通配.双星号正则表达式片段;
        }
        else {
            if (使用者 == "目录"d) {
                子模式 ~= "("d;
                ++可选计数;
            }
            if (有通配符组件) {
                子模式 ~= 路径模块.目录分割符;
            }
            if (使用者 != "排除"d) {
                dstring 组件模式 = ""d;
                // The * and ? wildcards should not 匹配 directories or 文件组 that start with . if they
                // appear first in a 组件. Dotted directories and 文件组 can be included explicitly
                // like so: **/.*/.*
                if (组件[0] == 字符_.星号) {
                    组件模式 ~= "([^./]"d ~ 通配.单星号正则表达式片段 ~ ")?"d;
                    组件 = 组件[1 .. $];
                }
                else if (组件[0] == 字符_.问号) {
                    组件模式 ~= "[^./]"d;
                    组件 = 组件[1 .. $];
                }
                组件模式 ~= 保留字符模式.p·替换全部(组件, cast(dstring delegate(匹配结果组_!(dstring)))(&通配.替换通配符));
                // Patterns should not 包括 subfolders like node_modules unless they are
                // explicitly included as part of the 路径.
                //
                // As an optimization, if the 组件 模式 is the same as the 组件,
                // then there definitely were no wildcard characters and we do not need to
                // add the exclusion 模式.
                if (组件模式 != 组件) {
                    子模式 ~= .隐式排除路径正则表达式模式;
                }
                子模式 ~= 组件模式;
            }
            else {
                子模式 ~= 保留字符模式.p·替换全部(组件, cast(dstring delegate(匹配结果组_!(dstring)))(&通配.替换通配符));
            }
        }
        有通配符组件 = true;
    }
    while(可选计数 > 0) {
        子模式 ~= ")?"d;
        --可选计数;
    }
    return 子模式;
}
/** getRegularExpressionForWildcard */
dstring 获取通配符的正则表达式(dstring[] 规范组 = ··null!(dstring[]), dstring 基路径 = ··null!(dstring), dstring 使用者 = ··null!(dstring)) {
    dstring[] 模式组 = .获取通配符的正则表达式组(规范组, 基路径, 使用者);
    if ((模式组 is ··null!(dstring[])) || 模式组.length == 0) {
        return ··null!(dstring);
    }
    dstring 模式 = 核心.连接(核心.映射!(dstring, dstring)(模式组, (dstring p, size_t _) { return `(`d ~ p ~ `)`d; }), "|"d);
    // If excluding, 匹配 "foo/bar/baz...", but if including, only allow "foo".
    dstring 结尾 = 使用者 == "排除"d ? "($|/)"d : "$"d;
    return `^(`d ~ 模式 ~ `)`d ~ 结尾 ~ ``d;
}
/** getFileMatcherPatterns */
文件匹配器模式_ 获取文件匹配模式组(dstring 路径, dstring[] 排除组 = ··null!(dstring[]), dstring[] 包扩组 = ··null!(dstring[]), bool 使用区分大小写文件名 = ··null!(bool), dstring 当前目录 = ··null!(dstring)) {
    路径 = 路径模块.正规化路径(路径);
    当前目录 = 路径模块.正规化路径(当前目录);
    dstring 绝对路径 = 路径模块.组合路径(当前目录, [路径]);
    return new 文件匹配器模式_(.获取通配符的正则表达式(排除组, 绝对路径, "排除"d), .获取基路径组(路径, 包扩组, 使用区分大小写文件名));
}
/** getBasePaths */
dstring[] 获取基路径组(dstring 路径, dstring[] 包扩组 = ··null!(dstring[]), bool 使用区分大小写文件名 = ··null!(bool)) {
    // Storage for our 结果组 in the form of literal paths (e.g. the paths as written by the user).
    dstring[] 基路径组 = [路径];
    if (包扩组 !is ··null!(dstring[])) {
        // Storage for literal base paths amongst the 包括 模式组.
        dstring[] 包含基路径组 = [];
        foreach (dstring 包括; 包扩组) {
            // We also need to check the relative paths by converting them to 绝对 and normalizing
            // in case they escape the base 路径 (e.g "..\somedirectory")
            dstring 绝对 = 路径模块.是根磁盘路径(包括) ? 包括 : 路径模块.正规化路径(路径模块.组合路径(路径, [包括]));
            // Append the literal and canonical candidate base paths.
            包含基路径组 = .追加!(dstring)(包含基路径组, .获取包括基路径(绝对));
        }
        // Sort the offsets array using either the literal or canonical 路径 representations.
        包含基路径组 = 核心.排序!(dstring)(包含基路径组, 核心.获取文本比较器(使用区分大小写文件名));
        // Iterate over each 包括 base 路径 and 包括 unique base paths that are not a
        // subpath of an existing base 路径
        foreach (dstring 包含基路径; 包含基路径组) {
            if (核心.包含一部分!(dstring)(基路径组, (dstring 基路径) { return !路径模块.包含路径(基路径, 包含基路径, 路径, !使用区分大小写文件名); })) {
                基路径组 = .追加!(dstring)(基路径组, 包含基路径);
            }
        }
    }
    return 基路径组;
}
/** getIncludeBasePath */
dstring 获取包括基路径(dstring 绝对) {
    long 通配符偏移量 = 核心.查找任意字符代码的索引(绝对, .通配符字符代码数组);
    if (通配符偏移量 < 0) {
        // No "*" or "?" in the 路径
        return !路径模块.有扩展名(绝对) ? 绝对 : 路径模块.删除尾随路径分隔符(路径模块.获取目录路径(绝对));
    }
    return 绝对[0 .. 核心.从后查找索引(绝对, 路径模块.目录分割符, 通配符偏移量)];
}
/** matchFiles */
dstring[] 匹配文件组(dstring 路径, dstring[] 扩展名, dstring[] 排除, dstring[] 包括, bool 使用区分大小写的文件名称, dstring 当前目录, int 深度, 文件系统条目_ delegate(dstring) 获取文件系统条目, dstring delegate(dstring) 绝对路径) {
    void delegate(dstring, dstring, int) 访问目录;
    文件匹配器模式_ 模式组;
    正则_!(dchar) 排除正则;
    核心.词典_!(bool) 访问过的;
    dstring[][] 结果组;
    核心.获取正规文件名函数_ 规范文件名函数;
    void 访问目录·func(dstring 路径, dstring 绝对的, int 深度 = 0) {
        dstring 标准路径;
        文件系统条目_ 文件条目;
        dstring[] 文件组;
        dstring[] 目录组;
        标准路径 = 规范文件名函数(绝对路径(绝对的));
        if (访问过的.具有(标准路径)) {
            return;
        }
        访问过的.p·设置(标准路径, true);
        文件条目 = 获取文件系统条目(路径);
        文件组 = 文件条目.文件组;
        目录组 = 文件条目.目录组;
        foreach (dstring 当前的; 核心.排序!(dstring)(文件组, cast(比较结果_ delegate(dstring, dstring))(·fd(&核心.比较文本区分大小写)))) {
            dstring 名称 = 路径模块.组合路径(路径, [当前的]);
            dstring 绝对名称 = 路径模块.组合路径(绝对的, [当前的]);
            if ((扩展名 !is ··null!(dstring[])) && 路径模块.获取任意一个扩展名从路径平台(名称, 扩展名, 使用区分大小写的文件名称 ? cast(bool delegate(dstring, dstring))(·fd(&核心.相等文本不区分大小写)) : cast(bool delegate(dstring, dstring))(·fd(&核心.相等值!(dstring)))) == ""d) {
                continue;
            }
            if ((排除正则 !is ··null!(正则_!(dchar))) && !排除正则.p·匹配(绝对名称).empty()) {
                continue;
            }
            结果组[0] = .追加!(dstring)(结果组[0], 名称);
        }
        if (深度 != 0) {
            --深度;
            if (深度 == 0) {
                return;
            }
        }
        foreach (dstring 当前的; 核心.排序!(dstring)(目录组, cast(比较结果_ delegate(dstring, dstring))(·fd(&核心.比较文本区分大小写)))) {
            dstring 名称 = 路径模块.组合路径(路径, [当前的]);
            dstring 绝对名称 = 路径模块.组合路径(绝对的, [当前的]);
            if (((排除正则 is ··null!(正则_!(dchar))) || 排除正则.p·匹配(绝对名称).empty())) {
                访问目录(名称, 绝对名称, 深度);
            }
        }
    }
    访问目录 = &访问目录·func;
    路径 = 路径模块.正规化路径(路径);
    当前目录 = 路径模块.正规化路径(当前目录);
    模式组 = .获取文件匹配模式组(路径, 排除, 包括, 使用区分大小写的文件名称, 当前目录);
    访问过的 = new 核心.词典_!(bool)();
    结果组 = [[]];
    规范文件名函数 = 核心.创建获取正规文件名函数(使用区分大小写的文件名称);
    if (模式组.排除模式 !is ··null!(dstring)) {
        排除正则 = .获取正则从模式(模式组.排除模式, 使用区分大小写的文件名称);
    }
    foreach (dstring 基路径; 模式组.基路径组) {
        访问目录(基路径, 路径模块.组合路径(当前目录, [基路径]), 深度);
    }
    return 核心.扁平化!(dstring)(结果组);
}
dstring getLocaleSpecificMessage(诊断消息_ message) {
    if ((.本地的诊断消息 !is ··null!(dstring[dstring])) && (.具有!(dstring, dstring)(.本地的诊断消息, message.键) !is ··null!(dstring*))) {
        dstring m = 本地的诊断消息[message.键];
        return m;
    }
    return message.消息;
}
dstring formatStringFromArgs(dstring text, dstring[] args) {
    正则_!(dchar) 正则 = ·regex(r"\{(\d+)\}"d, "g"c);
    if (!正则.p·匹配(text).empty()) {
        return 正则.p·替换全部(text, (匹配结果组_!(dstring) cap) { return args[编译数字.编译!(int)(cap[1])]; });
    }
    return text;
}
诊断_ 创建编译诊断(诊断消息_ message, dstring[] args) {
    args = args.dup;
    dstring text = .getLocaleSpecificMessage(message);
    if (args.length > 0 && args[0] != ""d) {
        text = .formatStringFromArgs(text, args);
    }
    return new 诊断_(message.诊断类别, message.代码, text);
}
dstring 格式化消息(诊断消息_ message, dstring[] args) {
    args = args.dup;
    dstring text = .getLocaleSpecificMessage(message);
    if (args.length > 0 && args[0] != ""d) {
        text = .formatStringFromArgs(text, args);
    }
    return text;
}
源文件_ 获取节点的源文件(节点_ 位置) {
    while(位置.种类 != SK.源文件) {
        位置 = 位置.父节点;
    }
    return (cast(源文件_)(位置));
}
dstring 获取节点的文本(节点_ 节点, bool 包括杂项 = false) {
    return .获取节点文本从源文件文本(.获取节点的源文件(节点).内容, 节点, 包括杂项);
}
dstring 获取节点文本从源文件文本(dstring 源文件文本, 节点_ 节点, bool 包括杂项 = false) {
    long 开始 = 包括杂项 ? 节点.开始 : 跳过杂项(源文件文本, 节点.开始, false, false);
    return 源文件文本[开始 .. 节点.结束];
}
诊断包括位置_ 创建节点的诊断(节点_ 节点, 诊断消息_ message, dstring[] args) {
    args = args.dup;
    源文件_ 文件 = .获取节点的源文件(节点);
    return .创建文件诊断消息(文件, 节点.开始, 节点.结束 - 节点.开始, message, args);
}
诊断包括位置_ 创建文件诊断消息(源文件_ file, long start, long length, 诊断消息_ message, dstring[] args) {
    args = args.dup;
    assert(start >= 0, "创建文件诊断消息 错误"c);
    assert(length >= 0, "创建文件诊断消息 错误"c);
    if (file !is ··null!(源文件_)) {
        assert(start <= file.内容.length, "创建文件诊断消息 错误"c);
        assert(start + length <= file.内容.length, "创建文件诊断消息 错误"c);
    }
    dstring text = .getLocaleSpecificMessage(message);
    if (args.length > 0 && args[0] != ""d) {
        text = .formatStringFromArgs(text, args);
    }
    return new 诊断包括位置_(file, start, length, message.诊断类别, message.代码, text);
}
路径模块.路径_ 获取诊断源文件路径(诊断_ 诊断) {
    if (诊断.文件 !is ··null!(Object)) {
        return (cast(源文件_)(诊断.文件)).文件名;
    }
    return ··null!(路径模块.路径_);
}
比较结果_ 比较诊断(诊断相关信息_ d1, 诊断相关信息_ d2) {
    诊断_ d_1 = (cast(诊断_)(d1));
    if (d_1 is ··null!(诊断_)) {
        return 比较结果_.小于;
    }
    诊断_ d_2 = (cast(诊断_)(d2));
    if (d_2 is ··null!(诊断_)) {
        return 比较结果_.大于;
    }
    比较结果_ ret = .比较诊断跳过相关消息(d_1, d_2);
    if (ret != 比较结果_.等于) {
        return ret;
    }
    return .比较诊断相关信息(d_1, d_2);
}
bool 比较诊断相等(诊断相关信息_ d1, 诊断相关信息_ d2) {
    比较结果_ ret = .比较诊断(d1, d2);
    if (ret == 比较结果_.等于) {
        return true;
    }
    return false;
}
比较结果_ 比较诊断跳过相关消息(诊断_ d1, 诊断_ d2) {
    路径模块.路径_ a = .获取诊断源文件路径(d1);
    if ((a is null) || (a.原型 is ··null!(dstring))) {
        return 比较结果_.小于;
    }
    路径模块.路径_ b = .获取诊断源文件路径(d2);
    if ((b is null) || (b.原型 is ··null!(dstring))) {
        return 比较结果_.大于;
    }
    比较结果_ ret = 核心.比较文本区分大小写(a.原型, b.原型);
    if (ret != 比较结果_.等于) {
        return ret;
    }
    ret = 核心.比较器比较值(d1.起点, d2.起点);
    if (ret != 比较结果_.等于) {
        return ret;
    }
    ret = 核心.比较器比较值(d1.长, d2.长);
    if (ret != 比较结果_.等于) {
        return ret;
    }
    ret = 核心.比较器比较值(d1.代码, d2.代码);
    if (ret != 比较结果_.等于) {
        return ret;
    }
    return .比较错误消息文本(d1.消息文本, d2.消息文本);
}
比较结果_ 比较诊断相关信息(诊断相关信息_ d1, 诊断相关信息_ d2) {
    诊断_ d_1 = (cast(诊断_)(d1));
    if (d_1 is ··null!(诊断_)) {
        return 比较结果_.小于;
    }
    诊断_ d_2 = (cast(诊断_)(d2));
    if (d_2 is ··null!(诊断_)) {
        return 比较结果_.大于;
    }
    if ((d_1.相关信息组 is ··null!(诊断相关信息_[])) && (d_2.相关信息组 is ··null!(诊断相关信息_[]))) {
        return 比较结果_.等于;
    }
    if ((d_1.相关信息组 !is ··null!(诊断相关信息_[])) && (d_2.相关信息组 !is ··null!(诊断相关信息_[]))) {
        比较结果_ ret = 核心.比较器比较值(d_1.相关信息组.length, d_2.相关信息组.length);
        if (ret != 比较结果_.等于) {
            return ret;
        }
        ret = 核心.循环每个!(诊断相关信息_, 比较结果_)(d_1.相关信息组, (诊断相关信息_ 诊断1, size_t 索引) {
            诊断相关信息_ 诊断2 = d_2.相关信息组[索引];
            return .比较诊断(诊断1, 诊断2);
        });
        return ret;
    }
    return d_1.相关信息组 ? 比较结果_.小于 : 比较结果_.大于;
}
比较结果_ 比较错误消息文本(诊断消息_链_ t1, 诊断消息_链_ t2) {
    比较结果_ ret = 核心.比较文本区分大小写(t1.消息文本, t2.消息文本);
    if (ret != 比较结果_.等于) {
        return ret;
    }
    if ((t1.下个 is ··null!(诊断消息_链_[])) && (t2.下个 is ··null!(诊断消息_链_[]))) {
        return 比较结果_.等于;
    }
    if (t1.下个 is ··null!(诊断消息_链_[])) {
        return 比较结果_.小于;
    }
    if (t2.下个 is ··null!(诊断消息_链_[])) {
        return 比较结果_.大于;
    }
    long 长 = 核心.最小(t1.下个.length, t2.下个.length);
    {
        for (int i = 0; i < 长; ++i) {
            ret = .比较错误消息文本(t1.下个[i], t2.下个[i]);
            if (ret != 比较结果_.等于) {
                return ret;
            }
        }
    }
    if (t1.下个.length < t2.下个.length) {
        return 比较结果_.小于;
    }
    else if (t1.下个.length > t2.下个.length) {
        return 比较结果_.大于;
    }
    return 比较结果_.等于;
}
T addRelatedInfo(T)(T diagnostic, 诊断相关信息_[] relatedInformation) {
    relatedInformation = relatedInformation.dup;
    if (relatedInformation.length > 0) {
        return diagnostic;
    }
    if (diagnostic.相关信息组 is ··null!(诊断相关信息_[])) {
        diagnostic.相关信息组 = 创建数组!(诊断相关信息_)();
    }
    foreach (诊断相关信息_ info; relatedInformation) {
        .追加!(诊断相关信息_)(diagnostic.相关信息组, info);
    }
    return diagnostic;
}
符号表_ 创建符号表(符号_[] 符号组 = ··null!(符号_[])) {
    核心.词典_!(符号_) 符号表 = 核心.创建词典!(符号_)();
    if (符号组 !is ··null!(符号_[])) {
        foreach (符号_ s; 符号组) {
            符号表.p·设置(s.名称, s);
        }
    }
    return ·to!(符号表_)(符号表);
}
bool isParseTreeNode(节点_ node) {
    return (node.标志 & 节点标志_.Synthesized) == 0;
}
节点_ getParseTreeNode_2(节点_ node = ··null!(节点_), bool delegate(节点_) nodeTest = ··null!(bool delegate(节点_))) {
    if ((node is ··null!(节点_)) || .isParseTreeNode(node)) {
        return node;
    }
    node = node.原始节点;
    while(node !is ··null!(节点_)) {
        if (.isParseTreeNode(node)) {
            return (nodeTest is ··null!(bool delegate(节点_))) || nodeTest(node) ? node : ··null!(节点_);
        }
        node = node.原始节点;
    }
    return ··null!(节点_);
}
节点_ getParseTreeNode_1(节点_ node = ··null!(节点_)) {
    return .getParseTreeNode_2(node, (节点_ n) { return n !is ··null!(节点_); });
}
